package com.jvxb.manage.livable.util;

import com.jvxb.common.constants.WorkOrderConstants;
import com.jvxb.manage.livable.vo.RedisCommandVO;
import org.apache.commons.lang3.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.exceptions.JedisMovedDataException;
import redis.clients.util.RedisOutputStream;
import redis.clients.util.SafeEncoder;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.*;

import static redis.clients.jedis.Protocol.ASTERISK_BYTE;
import static redis.clients.jedis.Protocol.DOLLAR_BYTE;

/**
 * @author jvxb
 * @since 2022-02-10
 */
public class RedisChangeUtil {

    private static Set<String> multiKey = new HashSet<>();

    public static final String KEY_TYPE_STRING ="string";
    public static final String KEY_TYPE_HASH ="hash";
    public static final String KEY_TYPE_LIST ="list";
    public static final String KEY_TYPE_SET ="set";
    public static final String KEY_TYPE_ZSET ="zset";

    //命令中包含多个key的集合
    static {
        multiKey.add("MSET");
        multiKey.add("MSETNX");
        multiKey.add("BRPOPLPUSH");
        multiKey.add("RPOPLPUSH");
    }

    //获取集合中的key
    public static List<String> getKey(String command, String type) {
        isBlankCommand(command);
        String commandName = getCommandName(command);
        if (multiKey.contains(commandName.toUpperCase())) {
            return getMultiKey(command, type, commandName);
        }
        String simpleKey = getSimpleKey(command);
        return Collections.singletonList(simpleKey);
    }

    private static void isBlankCommand(String command) {
        if (command == null || StringUtils.isBlank(command)) {
            throw new RuntimeException("空的命令行");
        }
    }

    public static String getCommandName(String command) {
        String[] split = command.split("\\s+", 2);
        return removeQuotes(split[0]);
    }

    public static List<String> getMultiKey(String command, String type, String commandName) {
        RedisCommandVO redisCommandVO = prepareCommand(command);
        Map<Integer, String> field = redisCommandVO.getField();
        String[] commandSplit = redisCommandVO.getPrepareCommand().split("\\s+");
        List<String> keys = new ArrayList<>();
        if (KEY_TYPE_STRING.equalsIgnoreCase(type) && commandSplit.length % 2 == 1) {
            int incr = 0;
            for (int i = 0; i < commandSplit.length; i++) {
                if (commandSplit[i].equals("?")) {
                    incr++;
                    if (i % 2 == 1) {
                        keys.add(removeQuotes(field.get(incr)));
                    }
                } else {
                    if (i % 2 == 1) {
                        keys.add(removeQuotes(commandSplit[i]));

                    }
                }
            }
            return keys;
        }
        if (KEY_TYPE_LIST.equalsIgnoreCase(type) && commandSplit.length == 3) {
            keys.add(removeQuotes(commandSplit[1]));
            keys.add(removeQuotes(commandSplit[2]));
            return keys;
        }
        return null;
    }

    private static String getSimpleKey(String command) {
        String[] commandSplit = command.split("\\s+");
        //如果key中带有'或者", 很有可能有空格，需特殊处理
        if (commandSplit[1].startsWith("'") || commandSplit[1].startsWith("\"")) {
            return getSpecialKey(command, commandSplit[1]);
        }
        return removeQuotes(commandSplit[1]);
    }

    private static String removeQuotes(String key) {
        if (key.startsWith("\"") && key.endsWith("\"")) {
            key = key.substring(1, key.length() - 1);
        }
        if (key.startsWith("'") && key.endsWith("'")) {
            key = key.substring(1, key.length() - 1);
        }
        return key;
    }

    public static RedisCommandVO prepareCommand(String command) {
        RedisCommandVO redisCommandVO = new RedisCommandVO();
        Map<Integer, String> fieldMap = new LinkedHashMap<>();
        isBlankCommand(command);
        char[] chars = command.toCharArray();
        int startIndex = -1;
        int endIndex = -1;
        int ascallIntValue = -1;
        //是否获取到第一个双引号
        boolean flag = false;
        String replaceStr = new String(command);
        int incr = 0;
        for (int i = 0; i < chars.length; i++) {
            //如果是"
            if (ascallIntValue == -1 && chars[i] == 34 && (i == 0 || chars[i - 1] == 32) && !flag) {
                startIndex = i;
                ascallIntValue = 34;
                flag = true;
            } else if (ascallIntValue == 34 && chars[i] == 34 && chars[i - 1] != 92) {
                endIndex = i;
            } else if (ascallIntValue == -1 && chars[i] == 39 && (i == 0 || chars[i - 1] == 32) && !flag) {
                startIndex = i;
                ascallIntValue = 39;
                flag = true;
            } else if (ascallIntValue == 39 && chars[i] == 39 && chars[i - 1] != 92) {
                endIndex = i;
            }

            if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
                String substring = command.substring(startIndex, endIndex + 1);
                fieldMap.put(incr, substring);
                incr++;
                if (substring.contains("\\")) {
                    substring = substring.replace("\\", "\\\\");
                }
                if (substring.contains("?")) {
                    substring = substring.replace("?", "\\?");
                }
                replaceStr = replaceStr.replaceFirst(substring, "?");
                startIndex = -1;
                endIndex = -1;
                flag = false;
                ascallIntValue = -1;
            }
        }
        redisCommandVO.setPrepareCommand(replaceStr);
        redisCommandVO.setField(fieldMap);
        return redisCommandVO;
    }

    private static String getSpecialKey(String command, String specialChar) {
        int beginCharPos = 0;
        int endCharPos = 0;
        char[] chars = command.toCharArray();
        for (int i = 1; i < chars.length; i++) {
            if (chars[i] == specialChar.charAt(0)) {
                if (beginCharPos == 0) {
                    beginCharPos = i;
                } else {
                    endCharPos = i;
                    break;
                }
            }
        }
        return command.substring(beginCharPos, endCharPos + 1);
    }

    //去掉换行符和分号
    public static List<String> getAllCommand(String text) {
        isBlankCommand(text);
        String[] commands = text.split("\\r?\\n");
        List<String> commandList = new ArrayList<>();
        for (String command : commands) {
            String commandTrim = command.trim();
            if (commandTrim.endsWith("；")) {
                throw new RuntimeException("请使用英文分号作为一行的结束符");
            }
            if (commandTrim.endsWith(";")) {
                commandTrim = commandTrim.substring(0, commandTrim.length() - 1);
            }
            commandTrim = commandTrim.trim();
            isBlankCommand(commandTrim);
            if (commandTrim.startsWith("del") || commandTrim.startsWith("DEL")) {
                String[] split = commandTrim.split("\\s+");
                if (split[1].startsWith("'") || split[1].startsWith("\"")) {
                    if(!split[split.length -1].endsWith("'") && !split[split.length -1].endsWith("\"")) {
                        throw new RuntimeException("每行仅支持删除一个key，删除多个key请用多行DEL来处理。");
                    }
                    commandList.add(split[0] + " " + getSpecialKey(command, split[1]));
                } else {
                    if(split.length > 2) {
                        throw new RuntimeException("每行仅支持删除一个key，删除多个key请用多行DEL来处理。");
                    }
                    commandList.add(split[0] + " " + split[1]);
                }
            } else {
                commandList.add(commandTrim);
            }

        }
        return commandList;

    }

    public static Socket movedExecute(String result) throws IOException {
        //判断是否需要拆分
        String[] s1 = result.split("\\s+");
        String movedAddr = s1[s1.length - 1];
        String[] movedAddrSplit = movedAddr.split(":");
        return getSocket(movedAddrSplit[0], Integer.parseInt(movedAddrSplit[1]));

    }

    //获取一个长连接的socket
    public static Socket getSocket(String ip, Integer port) throws IOException {
        Socket socket = new Socket();
        socket.setReuseAddress(true);
        socket.setKeepAlive(true);//保持TCP连接，不释放资源
        socket.setTcpNoDelay(true);//立即发送数据，不合并数据包
        socket.setSoLinger(true, 0);//强制关闭连接，不阻塞close(阻塞0s)
        socket.connect(new InetSocketAddress(ip, port), 2000);
        socket.setSoTimeout(2000);//读取数据阻塞超时时间2s(0是一直阻塞)
        return socket;
    }


    public static String execute(Socket socket, String command, String auth) {
        //close方法关闭Socket连接后，Socket对象所绑定的端口并不一定马上释放
        //系统有时在Socket连接关闭才会再确认一下是否有因为延迟面未到达的数据包
        //避免重启时，有时候的port already bind
        try {
            OutputStream outputStream = socket.getOutputStream();
            InputStream inputStream = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            if (auth != null) {
                format(outputStream, "auth " + auth);
                String authResult = br.readLine();
                if (authResult.contains("NOAUTH")) throw new RuntimeException("密码认证错误");
            }
            //判断command是否需要拆分
            format(outputStream, command);
            int startChar = inputStream.read();
            //-号返回错误信息
            if (startChar == 45) {
                return br.readLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return WorkOrderConstants.EXECUTE_FAILURE;
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return WorkOrderConstants.EXECUTE_SUCCESS;
    }

    //解析redis协议
    public static void format(OutputStream outputStream, String command) throws IOException {
        command = command.trim();
        RedisCommandVO redisCommandVO = prepareCommand(command);
        String prepareCommand = redisCommandVO.getPrepareCommand();
        Map<Integer, String> field = redisCommandVO.getField();
        String[] split = prepareCommand.split("\\s+");
        RedisOutputStream os = new RedisOutputStream(outputStream);
        os.write(ASTERISK_BYTE);
        os.writeIntCrLf(split.length);
        os.write(DOLLAR_BYTE);
        byte[] encode = SafeEncoder.encode(split[0]);
        os.writeIntCrLf(encode.length);
        os.write(encode);
        os.writeCrLf();
        int incr = 0;
        for (int i = 1; i < split.length; i++) {
            os.write(DOLLAR_BYTE);
            String s;
            if (split[i].equals("?")) {
                s = removeQuotes(field.get(incr));
                incr++;
            } else {
                s = removeQuotes(split[i]);
            }
            os.writeIntCrLf(s.getBytes("UTF-8").length);
            os.write(s.getBytes("UTF-8"));
            os.writeCrLf();
        }
        os.flush();
    }

    public static List<String> splitCommand(String command, String splitNum) {
        int num = Integer.parseInt(splitNum);
        List<String> resultList = new ArrayList<>();
        String commandName = getCommandName(command);
        if ("HMSET".equals(commandName.toUpperCase())) {
            RedisCommandVO redisCommandVO = prepareCommand(command);
            String prepareCommand = redisCommandVO.getPrepareCommand();
            Map<Integer, String> field = redisCommandVO.getField();
            String[] commandSplit = prepareCommand.split("\\s+");
            int foreachNum;
            if ((commandSplit.length - 1) / 2 > num) {
                if ((commandSplit.length - 1) / 2 % num == 0) {
                    foreachNum = (commandSplit.length - 1) / 2 / num;
                } else {
                    foreachNum = (commandSplit.length - 1) / 2 / num + 1;
                }
                for (int i = 0; i < foreachNum; i++) {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.append("HMSET ");
                    int incr = 0;
                    for (int j = 1 + 2 * i * num; j < ((1 + 2 * i * num + 2 * num) > commandSplit.length ? commandSplit.length : 1 + 2 * i * num +
                            2 * num); j++) {
                        if (commandSplit[j].equals("?")) {
                            stringBuilder.append(field.get(incr) + " ");
                            incr++;
                        } else {
                            stringBuilder.append(commandSplit[j] + " ");
                        }
                    }
                    resultList.add(stringBuilder.toString());
                }
                return resultList;
            }
        }

        return Collections.singletonList(command);
    }

    public static byte[] getDumpData(Jedis jedis, String key, String auth) {
        try {
            if (auth != null) jedis.auth(auth);
            byte[] dump = jedis.dump(key);
            return dump;
        } catch (JedisMovedDataException e) {
            Jedis movedJedis = null;
            try {
                movedJedis = new Jedis(e.getTargetNode().getHost(), e.getTargetNode().getPort());
                if (auth != null) movedJedis.auth(auth);
                return movedJedis.dump(key);
            } finally {
                if (movedJedis != null) movedJedis.close();
            }
        } finally {
            if (jedis != null) jedis.close();
        }
    }

    public static Long getTtl(Jedis jedis, String key, String auth) {
        try {
            if (auth != null) jedis.auth(auth);
            long ttl = jedis.ttl(key);
            return ttl;
        } catch (JedisMovedDataException e) {
            Jedis movedJedis = null;
            try {
                movedJedis = new Jedis(e.getTargetNode().getHost(), e.getTargetNode().getPort());
                if (auth != null) movedJedis.auth(auth);
                return movedJedis.ttl(key);
            } finally {
                if (movedJedis != null) movedJedis.close();
            }
        }
    }

    public static Long getTtlAndCloseJedis(Jedis jedis, String key, String auth) {
        try {
            if (auth != null) jedis.auth(auth);
            long ttl = jedis.ttl(key);
            return ttl;
        } catch (JedisMovedDataException e) {
            Jedis movedJedis = null;
            try {
                movedJedis = new Jedis(e.getTargetNode().getHost(), e.getTargetNode().getPort());
                if (auth != null) movedJedis.auth(auth);
                return movedJedis.ttl(key);
            } finally {
                if (movedJedis != null) movedJedis.close();
            }
        } finally {
            if (jedis != null) jedis.close();
        }
    }

    public static Long delKey(Jedis jedis, String key, String auth) {
        try {
            if (auth != null) jedis.auth(auth);
            return jedis.del(key);
        } catch (JedisMovedDataException e) {
            Jedis movedJedis = null;
            try {
                movedJedis = new Jedis(e.getTargetNode().getHost(), e.getTargetNode().getPort());
                if (auth != null) movedJedis.auth(auth);
                return movedJedis.del(key);
            } finally {
                if (movedJedis != null) movedJedis.close();
            }
        } finally {
            if (jedis != null) jedis.close();
        }
    }

    public static String rollbackByKey(Jedis jedis, String key, Long ttl, byte[] value, String auth) {
        if (ttl == -1) {
            ttl = 0L;
        }
        try {
            if (auth != null) jedis.auth(auth);
            return jedis.restore(key, Math.toIntExact(ttl), value);
        } catch (JedisMovedDataException e) {
            Jedis movedJedis = null;
            try {
                movedJedis = new Jedis(e.getTargetNode().getHost(), e.getTargetNode().getPort());
                if (auth != null) movedJedis.auth(auth);
                return movedJedis.restore(key, Math.toIntExact(ttl), value);
            } finally {
                if (movedJedis != null) movedJedis.close();
            }
        } finally {
            if (jedis != null) jedis.close();
        }
    }

    public static void closePipeline(Pipeline pipeline) {
        if (pipeline != null) {
            try {
                pipeline.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void closeJedisCluster(JedisCluster jedisCluster) {
        if (jedisCluster != null) {
            try {
                jedisCluster.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
